{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "activated-steering",
   "metadata": {},
   "source": [
    "# Requirements"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "blank-finding",
   "metadata": {},
   "outputs": [],
   "source": [
    "import tenseal as ts # pip install tenseal\n",
    "from deepface import DeepFace #!pip install deepface\n",
    "import base64\n",
    "from deepface.commons import distance as dst"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "assured-coating",
   "metadata": {},
   "source": [
    "# Finding embeddings\n",
    "\n",
    "We are going to find vector representations of facial images. This will be done in the client side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "invalid-scheme",
   "metadata": {},
   "outputs": [],
   "source": [
    "img1_path = \"deepface/tests/dataset/img1.jpg\"\n",
    "img2_path = \"deepface/tests/dataset/img2.jpg\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "coastal-oxygen",
   "metadata": {},
   "outputs": [],
   "source": [
    "img1_embedding = DeepFace.represent(img1_path, model_name = 'Facenet')\n",
    "img2_embedding = DeepFace.represent(img2_path, model_name = 'Facenet')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "considered-commitment",
   "metadata": {},
   "source": [
    "# Commons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "arabic-duration",
   "metadata": {},
   "outputs": [],
   "source": [
    "def write_data(file_name, data):\n",
    "    \n",
    "    if type(data) == bytes:\n",
    "        #bytes to base64\n",
    "        data = base64.b64encode(data)\n",
    "        \n",
    "    with open(file_name, 'wb') as f: \n",
    "        f.write(data)\n",
    "\n",
    "def read_data(file_name):\n",
    "    with open(file_name, \"rb\") as f:\n",
    "        data = f.read()\n",
    "    \n",
    "    #base64 to bytes\n",
    "    return base64.b64decode(data)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "understanding-fight",
   "metadata": {},
   "source": [
    "# Initialization\n",
    "\n",
    "We are going to generate secret - public key pair in this stage. This will be done in the client side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "manual-endorsement",
   "metadata": {},
   "outputs": [],
   "source": [
    "context = ts.context(\n",
    "            ts.SCHEME_TYPE.CKKS,\n",
    "            poly_modulus_degree = 8192,\n",
    "            coeff_mod_bit_sizes = [60, 40, 40, 60]\n",
    "          )\n",
    "\n",
    "context.generate_galois_keys()\n",
    "context.global_scale = 2**40"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "mental-mining",
   "metadata": {},
   "outputs": [],
   "source": [
    "secret_context = context.serialize(save_secret_key = True)\n",
    "write_data(\"secret.txt\", secret_context)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "magnetic-relevance",
   "metadata": {},
   "outputs": [],
   "source": [
    "context.make_context_public() #drop the secret_key from the context\n",
    "public_context = context.serialize()\n",
    "write_data(\"public.txt\", public_context)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "tropical-cooking",
   "metadata": {},
   "outputs": [],
   "source": [
    "del context, secret_context, public_context"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "exempt-rochester",
   "metadata": {},
   "source": [
    "# Encryption\n",
    "\n",
    "We are going to apply homomorphic encryption to facial embeddings. This will be done in the client side. \n",
    "\n",
    "Then, homomorphic encrypted facial embeddings will be stored in the cloud."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "stretch-press",
   "metadata": {},
   "outputs": [],
   "source": [
    "context = ts.context_from(read_data(\"secret.txt\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "joined-slovenia",
   "metadata": {},
   "outputs": [],
   "source": [
    "enc_v1 = ts.ckks_vector(context, img1_embedding)\n",
    "enc_v2 = ts.ckks_vector(context, img2_embedding)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "synthetic-adult",
   "metadata": {},
   "outputs": [],
   "source": [
    "enc_v1_proto = enc_v1.serialize()\n",
    "enc_v2_proto = enc_v2.serialize()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "phantom-return",
   "metadata": {},
   "outputs": [],
   "source": [
    "write_data(\"enc_v1.txt\", enc_v1_proto)\n",
    "write_data(\"enc_v2.txt\", enc_v2_proto)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "taken-shannon",
   "metadata": {},
   "outputs": [],
   "source": [
    "del context, enc_v1, enc_v2, enc_v1_proto, enc_v2_proto"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "informational-measure",
   "metadata": {},
   "source": [
    "# Calculations\n",
    "\n",
    "Once homomorphic encrypted facial embeddings stored in the cloud, we are able to make calculations on encrypted data.\n",
    "\n",
    "Notice that we just have public key here and we don't have secret key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "painful-constitution",
   "metadata": {},
   "outputs": [],
   "source": [
    "context = ts.context_from(read_data(\"public.txt\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "female-exchange",
   "metadata": {},
   "outputs": [],
   "source": [
    "enc_v1_proto = read_data(\"enc_v1.txt\")\n",
    "enc_v2_proto = read_data(\"enc_v2.txt\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "passing-sixth",
   "metadata": {},
   "outputs": [],
   "source": [
    "enc_v1 = ts.lazy_ckks_vector_from(enc_v1_proto)\n",
    "enc_v1.link_context(context)\n",
    "\n",
    "enc_v2 = ts.lazy_ckks_vector_from(enc_v2_proto)\n",
    "enc_v2.link_context(context)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "built-configuration",
   "metadata": {},
   "outputs": [],
   "source": [
    "euclidean_squared = enc_v1 - enc_v2\n",
    "euclidean_squared = euclidean_squared.dot(euclidean_squared)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "synthetic-significance",
   "metadata": {},
   "outputs": [],
   "source": [
    "write_data(\"euclidean_squared.txt\", euclidean_squared.serialize())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "inside-rendering",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Exception:  the current context of the tensor doesn't hold a secret_key, please provide one as argument\n"
     ]
    }
   ],
   "source": [
    "#we must not decrypt the homomorphic encrypted euclidean squared value in this stage\n",
    "#because we don't have the secret key. check this operation. it should throw an exception!\n",
    "\n",
    "try:\n",
    "    euclidean_squared.decrypt()\n",
    "except Exception as err:\n",
    "    print(\"Exception: \", str(err))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "experimental-criterion",
   "metadata": {},
   "outputs": [],
   "source": [
    "del context, enc_v1_proto, enc_v2_proto, enc_v1, enc_v2, euclidean_squared"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "compatible-handle",
   "metadata": {},
   "source": [
    "# Decryption\n",
    "\n",
    "Once homomorphic encrypted euclidean squared value found in the cloud, we are going to retrieve it to the client side.\n",
    "\n",
    "Client can decrypt it because we have the secret key in the client side."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "intellectual-shadow",
   "metadata": {},
   "outputs": [],
   "source": [
    "context = ts.context_from(read_data(\"secret.txt\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "welcome-question",
   "metadata": {},
   "outputs": [],
   "source": [
    "euclidean_squared_proto = read_data(\"euclidean_squared.txt\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "eligible-judges",
   "metadata": {},
   "outputs": [],
   "source": [
    "euclidean_squared = ts.lazy_ckks_vector_from(euclidean_squared_proto)\n",
    "euclidean_squared.link_context(context)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "phantom-impression",
   "metadata": {},
   "outputs": [],
   "source": [
    "euclidean_squared_plain = euclidean_squared.decrypt()[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "eleven-psychiatry",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "66.36774485705642"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "euclidean_squared_plain"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "changed-floor",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "they are same person\n"
     ]
    }
   ],
   "source": [
    "if euclidean_squared_plain < 100:\n",
    "    print(\"they are same person\")\n",
    "else:\n",
    "    print(\"they are different persons\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "federal-appointment",
   "metadata": {},
   "source": [
    "# Validation\n",
    "\n",
    "What if euclidean distance calculation is done in the client side always? Result should be same!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "sexual-johns",
   "metadata": {},
   "outputs": [],
   "source": [
    "distance = dst.findEuclideanDistance(img1_embedding, img2_embedding)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "welsh-evaluation",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "euclidean squared - tradational:  66.3677359167053\n",
      "euclidean squared - homomorphic:  66.36774485705642\n"
     ]
    }
   ],
   "source": [
    "print(\"euclidean squared - tradational: \", distance * distance)\n",
    "print(\"euclidean squared - homomorphic: \", euclidean_squared_plain)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "difficult-compilation",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#check the difference is acceptable\n",
    "abs(distance * distance - euclidean_squared_plain) < 0.00001"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "hungry-principle",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
